#region Copyright RenGuiYou. All rights reserved.
//=====================================================
// NeatlyFrameWork
// Author:      RenGuiyou
// Feedback: 	mailto:750539605@qq.com
//=====================================================
#endregion
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Globalization;
using System.Text;
using Neatly.UI;
using UnityEngine.UI;

namespace Neatly.Tween
{
    public enum TweenType
    {
        Normal = 0,
        EaseOutQuint = 2,
        EaseOutCubic = 3,
        EaseInSine = 4,
        EaseOutSine = 5,
        EaseInOutSine = 6,
        EaseInQuad = 7,
        EaseOutQuad = 8,
        EaseInOutQuad = 9,
    }

    public enum LineType
    {
        Lerp = 0,
        Bezier = 1,
    }

    /// <summary>
    /// 自定义移动
    /// </summary>
    internal sealed class TweenFuncPosObj : TweenObjectBase
    {
        private RectTransform _rectTransform;
        private CanvasGroup _canvasGroup;
        private float _startY;
        private float _targetY;
        private float _time;
        private NeatlyTween.PositionDelegate _posResult;
        private NeatlyTween.AlphaDelegate _alphaResult;

        public static TweenFuncPosObj Create(RectTransform rectTransform, float targetY, float time,
            NeatlyTween.PositionDelegate posResult, NeatlyTween.AlphaDelegate alphaResult)
        {
            var tweenObj = new TweenFuncPosObj
            {
                gameObject = rectTransform.gameObject,
                _rectTransform = rectTransform,
                _canvasGroup = rectTransform.GetComponent<CanvasGroup>(),
                _startY = rectTransform.anchoredPosition.y,
                _targetY = targetY,
                _dt = 0,
                _time = time,
                _posResult = posResult,
                _alphaResult = alphaResult,
            };
            return tweenObj;
        }

        public override bool Execute(float dt)
        {
            _dt += dt;
            float y = Mathf.Lerp(_startY, _targetY, _dt / _time);
            _canvasGroup.alpha = _alphaResult(y);
            _rectTransform.anchoredPosition = _posResult(y);
            return _dt >= _time;
        }
    }

    public sealed class TweenRtPosObj : TweenObjectBase
    {
        private enum MoveType
        {
            Anchored, //UI
            Position, //世界坐标
            LocalPosition, //区域坐标
            Scale,
            Rotate,
            AutoRotate,
            ImageColor,
            SpriteColor,
            SizeDelta,
        }

        private RectTransform _rectTransform;
        private Transform _transform;
        private Vector3 _startV3;
        private Vector3 _targetV3;
        private Vector3 _bezierV3;
        private List<Vector3> _bezierList;
        #region Color
        private Graphic _graphic;
        private SpriteRenderer _renderer;
        private Color _startColor;
        private Color _targetColor;
        #endregion
        private float _time;
        private MoveType _moveType;
        private LineType _lineType;
        private TweenType _tweenType;

        public static TweenRtPosObj CreateMove(RectTransform rectTransform, Vector2 targetVal, float time, int tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = rectTransform.gameObject;
            tweenObj._rectTransform = rectTransform;
            tweenObj._startV3 = rectTransform.anchoredPosition;
            tweenObj._targetV3 = targetVal;
            tweenObj._dt = 0;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.Anchored;
            tweenObj._lineType = LineType.Lerp;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }

        public static TweenRtPosObj CreateMove(Component behaviour, Vector2 targetVal, float time, int tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = behaviour.gameObject;
            tweenObj._rectTransform = behaviour.transform.rectTransform();
            tweenObj._startV3 = behaviour.transform.rectTransform().anchoredPosition;
            tweenObj._targetV3 = targetVal;
            tweenObj._dt = 0;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.Anchored;
            tweenObj._lineType = LineType.Lerp;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }

        public static TweenRtPosObj CreateUIBezierMove(RectTransform rectTransform, Vector2 targetVal, Vector2 bezierVal, float time, float tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = rectTransform.gameObject;
            tweenObj._rectTransform = rectTransform;
            tweenObj._startV3 = rectTransform.anchoredPosition;
            tweenObj._targetV3 = targetVal;
            tweenObj._dt = 0;
            tweenObj._bezierV3 = bezierVal;
            tweenObj._bezierList = null;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.Anchored;
            tweenObj._lineType = LineType.Bezier;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }
        
        public static TweenRtPosObj CreateUIBezierMove(RectTransform rectTransform, Vector2 targetVal, List<Vector3> bezierList, float time, float tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = rectTransform.gameObject;
            tweenObj._rectTransform = rectTransform;
            tweenObj._startV3 = rectTransform.anchoredPosition;
            tweenObj._targetV3 = targetVal;
            tweenObj._dt = 0;
            tweenObj._bezierList = bezierList;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.Anchored;
            tweenObj._lineType = LineType.Bezier;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }

        public static TweenRtPosObj CreateBezierMove(GameObject gameObject, Vector3 targetVal, List<Vector3> bezierList, float time, float tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = gameObject;
            tweenObj._transform = gameObject.transform;
            tweenObj._startV3 = gameObject.transform.position;
            tweenObj._targetV3 = targetVal;
            tweenObj._dt = 0;
            tweenObj._bezierList = bezierList;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.Position;
            tweenObj._lineType = LineType.Bezier;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }

        public static TweenRtPosObj CreateMove(GameObject gameObject, Vector3 targetVal, float time, float tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = gameObject;
            tweenObj._transform = gameObject.transform;
            tweenObj._startV3 = gameObject.transform.position;
            tweenObj._targetV3 = targetVal;
            tweenObj._dt = 0;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.Position;
            tweenObj._lineType = LineType.Lerp;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }

        public static TweenRtPosObj CreateMoveLocal(GameObject gameObject, Vector3 targetVal, float time, float tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = gameObject;
            tweenObj._transform = gameObject.transform;
            tweenObj._startV3 = gameObject.transform.localPosition;
            tweenObj._targetV3 = targetVal;
            tweenObj._dt = 0;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.LocalPosition;
            tweenObj._lineType = LineType.Lerp;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }

        public static TweenRtPosObj CreateScale(Transform transform, Vector3 targetVal, float time, float tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = transform.gameObject;
            tweenObj._transform = transform;
            tweenObj._startV3 = transform.localScale;
            tweenObj._targetV3 = targetVal;
            tweenObj._dt = 0;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.Scale;
            tweenObj._lineType = LineType.Lerp;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }

        public static TweenRtPosObj CreateScaleEaseOutQuint(Transform transform, Vector3 targetVal, float time)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = transform.gameObject;
            tweenObj._transform = transform;
            tweenObj._startV3 = transform.localScale;
            tweenObj._targetV3 = targetVal;
            tweenObj._dt = 0;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.Scale;
            tweenObj._lineType = LineType.Lerp;
            tweenObj._tweenType = TweenType.EaseOutQuint;
            return tweenObj;
        }

        public static TweenRtPosObj CreateRotate(Transform transform, Vector3 targetVal, float time, float tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = transform.gameObject;
            tweenObj._transform = transform;
            tweenObj._startV3 = transform.localEulerAngles;
            tweenObj._targetV3 = targetVal;
            tweenObj._dt = 0;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.Rotate;
            tweenObj._lineType = LineType.Lerp;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }

        public static TweenRtPosObj CreateAutoRotate(Transform transform, Vector3 targetVal, float time, float tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = transform.gameObject;
            tweenObj._transform = transform;
            tweenObj._startV3 = transform.localEulerAngles;
            tweenObj._targetV3 = targetVal;
            tweenObj._dt = 0;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.AutoRotate;
            tweenObj._lineType = LineType.Lerp;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }

        public static TweenRtPosObj CreateColor(Graphic graphic, Color targetVal, float time, float tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = graphic.gameObject;
            tweenObj._graphic = graphic;
            tweenObj._startColor = graphic.color;
            tweenObj._targetColor = targetVal;
            tweenObj._dt = 0;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.ImageColor;
            tweenObj._lineType = LineType.Lerp;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }

        public static TweenRtPosObj CreateColor(SpriteRenderer graphic, Color targetVal, float time, float tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = graphic.gameObject;
            tweenObj._renderer = graphic;
            tweenObj._startColor = graphic.color;
            tweenObj._targetColor = targetVal;
            tweenObj._dt = 0;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.SpriteColor;
            tweenObj._lineType = LineType.Lerp;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }

        public static TweenRtPosObj CreateSizeDelta(RectTransform rectTransform, Vector2 targetVal, float time, float tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = rectTransform.gameObject;
            tweenObj._rectTransform = rectTransform;
            tweenObj._startV3 = rectTransform.sizeDelta;
            tweenObj._targetV3 = targetVal;
            tweenObj._dt = 0;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.SizeDelta;
            tweenObj._lineType = LineType.Lerp;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }

        public static TweenRtPosObj CreateSizeDelta(Component cmp, Vector2 targetVal, float time, float tweenType = 0)
        {
            var tweenObj = NeatlyTween.Instance.TweenRtPosPool.Create();
            tweenObj.gameObject = cmp.gameObject;
            tweenObj._rectTransform = cmp.rectTransform();
            tweenObj._startV3 = cmp.rectTransform().sizeDelta;
            tweenObj._targetV3 = targetVal;
            tweenObj._dt = 0;
            tweenObj._time = time;
            tweenObj._moveType = MoveType.SizeDelta;
            tweenObj._lineType = LineType.Lerp;
            tweenObj._tweenType = (TweenType)tweenType;
            return tweenObj;
        }

        public override bool Execute(float dt)
        {
            _dt += dt;
            Vector3 val = Vector3.zero;
            if (_moveType != MoveType.Rotate && _moveType != MoveType.ImageColor)
            {
                float ratio = NMathf.Clamp(_dt / _time, 0, 1);
                ratio = TweenHelper.GetTweenTypeRatio(_tweenType, ratio);
                if (_lineType == LineType.Lerp)
                {
                    val = NMathf.Lerp(_startV3, _targetV3, ratio);
                }
                else if (_lineType == LineType.Bezier)
                {
                    if (_bezierList != null)
                    {
                        val = TweenHelper.Bezier(ratio, _bezierList, false); 
                        _rectTransform.SetLocalEulerAnglesZ(-NMathf.Vector2Angle(val, _rectTransform.GetAnchoredPosition())-90);
                    }
                    else
                    {
                        var p1 = NMathf.Lerp(_startV3, _bezierV3, ratio);
                        var p2 = NMathf.Lerp(_bezierV3, _targetV3, ratio);
                        val = NMathf.Lerp(p1, p2, ratio);
                    }
                }
            }
            switch (_moveType)
            {
                case MoveType.Anchored:
                    _rectTransform.anchoredPosition = val;
                    break;
                case MoveType.Position:
                    _transform.position = val;
                    break;
                case MoveType.LocalPosition:
                    _transform.localPosition = val;
                    break;
                case MoveType.Scale:
                    _transform.localScale = val;
                    break;
                case MoveType.Rotate:
                    _transform.localEulerAngles = NMathf.LerpAngle(_startV3, _targetV3, _dt, _time);
                    break;
                case MoveType.AutoRotate:
                    _transform.localEulerAngles = val;
                    break;
                case MoveType.ImageColor:
                    _graphic.color = NMathf.Lerp(_startColor, _targetColor, _dt, _time);
                    break;
                case MoveType.SizeDelta:
                    _rectTransform.sizeDelta = val;
                    break;
                case MoveType.SpriteColor:
                    _renderer.color = NMathf.Lerp(_startColor, _targetColor, _dt, _time);
                    break;
            }
            return _dt >= _time;
        }

        public override void Destroy()
        {
            NeatlyTween.Instance.TweenRtPosPool.Recycle(this);
        }

        public override void DoPingPong()
        {
            if (_moveType == MoveType.ImageColor)
            {
                (_startColor, _targetColor) = (_targetColor, _startColor);
            }
            else
            {
                (_startV3, _targetV3) = (_targetV3, _startV3);
            }
        }
    }

    public sealed class TweenMultiProgress : TweenObjectBase
    {
        private enum TweenType
        {
            FillAmount, 
            SizeDelta,
        }

        private NImage m_Image;
        private float m_Time;
        private float m_StartVal;
        private float m_TargetVal;
        private float m_SingleProgress;
        private int m_LastMultiCount;
        private int m_MaxWidth;
        private TweenType m_TweenType;
        private Action<int> m_OnChangeAction;

        public static TweenMultiProgress CreateFillAmount(NImage image, int lastMultiCount, float targetVal, int multiCount, Action<int> changeAction, float time)
        {
            TweenMultiProgress obj = new TweenMultiProgress
            {
                gameObject = image.gameObject,
                m_Image = image,
                m_TargetVal = targetVal,
                m_OnChangeAction = changeAction,
                m_SingleProgress = 1.0f / multiCount,
                m_LastMultiCount = lastMultiCount,
                m_Time = time,
                _dt = 0,
                m_TweenType = TweenType.FillAmount,
            };
            obj.m_StartVal = obj.m_SingleProgress * (lastMultiCount + image.fillAmount);
            changeAction?.Invoke(lastMultiCount);
            return obj;
        }

        public static TweenMultiProgress CreateSizeDelta(NImage image,int maxWidth, int lastMultiCount, float targetVal, int multiCount, Action<int> changeAction, float time)
        {
            TweenMultiProgress obj = new TweenMultiProgress
            {
                gameObject = image.gameObject,
                m_Image = image,
                m_MaxWidth = maxWidth,
                m_TargetVal = targetVal,
                m_OnChangeAction = changeAction,
                m_SingleProgress = 1.0f / multiCount,
                m_LastMultiCount = lastMultiCount,
                m_Time = time,
                _dt = 0,
                m_TweenType = TweenType.SizeDelta,
            };
            obj.m_StartVal = obj.m_SingleProgress * (lastMultiCount + image.GetSizeDeltaX()/maxWidth);
            changeAction?.Invoke(lastMultiCount);
            return obj;
        }
        public override bool Execute(float dt)
        {
            _dt += dt;
            float percent = NMathf.Lerp(m_StartVal, m_TargetVal, _dt / m_Time);
            int multiCount = (int)(percent / m_SingleProgress);
            if (multiCount != m_LastMultiCount)
            {
                m_OnChangeAction?.Invoke(multiCount);
            }
            m_LastMultiCount = multiCount;
            float value = (percent - (multiCount * m_SingleProgress))/m_SingleProgress;
            if (m_TweenType == TweenType.FillAmount)
            {
                m_Image.SetFillAmount(value);
            }else if (m_TweenType == TweenType.SizeDelta)
            {
                m_Image.SetSizeDeltaX(value*m_MaxWidth);
            }
            return _dt >= m_Time;
        }
    }

    /// <summary>
    /// 文本跳动
    /// </summary>
    internal sealed class TweenJumpObj : TweenObjectBase
    {
        private enum JumpType
        {
            Normal,
            Format,
        }

        private TextInterface _text;
        private double _startVal;
        private double _targetVal;
        private float _time;
        private string _beforeLabel;
        private string _afterLabel;
        private JumpType _jumpType;

        public static TweenJumpObj Create(TextInterface text, double startVal, double targetVal, float time, string beforeLabel = null, string afterLabel = null)
        {
            TweenJumpObj obj = new TweenJumpObj
            {
                gameObject = text.GetGameObject(),
                _text = text,
                _startVal = startVal,
                _targetVal = targetVal,
                _time = time,
                _dt = 0,
                _beforeLabel = beforeLabel,
                _afterLabel = afterLabel,
                _jumpType = JumpType.Normal,
            };
            return obj;
        }

        public static TweenJumpObj CreateFormat(TextInterface text, double startVal, double targetVal, float time, string beforeLabel = null, string afterLabel = null)
        {
            TweenJumpObj obj = new TweenJumpObj
            {
                gameObject = text.GetGameObject(),
                _text = text,
                _startVal = startVal,
                _targetVal = targetVal,
                _time = time,
                _dt = 0,
                _beforeLabel = beforeLabel,
                _afterLabel = afterLabel,
                _jumpType = JumpType.Format,
            };
            return obj;
        }

        public override bool Execute(float dt)
        {
            _dt += dt;
            long value = (long)NMathf.Lerp(_startVal, _targetVal, _dt / _time);
            string label = null;
            if (_jumpType == JumpType.Normal)
            {
                label = $"{value}";
            }
            else if (_jumpType == JumpType.Format)
            {
                label = FormatString(value);
            }
            if (!string.IsNullOrEmpty(_beforeLabel))
            {
                label = _beforeLabel + value;
            }
            if (!string.IsNullOrEmpty(_afterLabel))
            {
                label += _afterLabel;
            }
            _text.SetText(label);
            return _dt >= _time;
        }

        private static readonly string[] FORMAT_ARRAY = { "K", "M", "B" };

        private string FormatString(long num)
        {
            for (int i = FORMAT_ARRAY.Length - 1; i >= 0; i--)
            {
                long val = num / (1000 ^ i);
                if (val >= 10)
                {
                    return $"{val}{FORMAT_ARRAY[i]}";
                }
            }
            return num.ToString();
        }

        private string GetJumpVal(string str)
        {
            StringBuilder sb = new StringBuilder();
            for (int t = 1, i = str.Length - 1; i >= 0; i--, t++)
            {
                sb.Insert(0, str[i]);
                if (t % 3 == 0 && i > 0)
                {
                    sb.Insert(0, ",");
                }
            }
            return sb.ToString();
        }
    }

    /// <summary>
    /// Slider滑动(向目标)
    /// </summary>
    internal sealed class TweenSliderObj : TweenObjectBase
    {
        private NSlider _slider;
        private float _startVal;
        private float _targetVal;
        private float _time;

        public static TweenSliderObj Create(NSlider slider, float targetVal, float time)
        {
            TweenSliderObj obj = new TweenSliderObj
            {
                gameObject = slider.gameObject,
                _slider = slider,
                _startVal = slider.value,
                _targetVal = targetVal,
                _time = time,
                _dt = 0,
            };
            return obj;
        }

        public override bool Execute(float dt)
        {
            _dt += dt;
            float val = Mathf.Lerp(_startVal, _targetVal, _dt / _time);
            _slider.SetValue(val);
            return _dt >= _time;
        }
    }

    /// <summary>
    /// Slider滑动(向终点值)
    /// </summary>
    internal sealed class TweenSliderDirectObj : TweenObjectBase
    {
        private Slider _slider;
        private float _startVal;
        private float _targetVal;
        private float _time;
        private float _remainTime;
        private float _curTargetVal;
        private bool _direct;
        private bool _twice;

        public static TweenSliderDirectObj Create(Slider slider, float targetVal, float time, bool direct)
        {
            TweenSliderDirectObj obj = new TweenSliderDirectObj
            {
                gameObject = slider.gameObject,
                _slider = slider,
                _startVal = slider.value,
                _targetVal = targetVal,
                _dt = 0,
                _direct = direct,
                _twice = direct ? targetVal < slider.value : targetVal > slider.value,
            };
            if (obj._twice)
            {
                if (direct)
                {
                    float dis = targetVal + 1 - obj._startVal;
                    obj._time = (1 - obj._startVal) / dis * time;
                }
                else
                {
                    float dis = obj._startVal + 1 - targetVal;
                    obj._time = obj._startVal / dis * time;
                }
                obj._curTargetVal = direct ? 1 : 0;
            }
            else
            {
                obj._time = time;
                obj._curTargetVal = targetVal;
            }
            obj._remainTime = time - obj._time;
            return obj;
        }

        public override bool Execute(float dt)
        {
            _dt += dt;
            float val = Mathf.Lerp(_startVal, _curTargetVal, _dt / _time);
            _slider.value = val;
            if (_twice && _dt >= _time)
            {
                _startVal = _direct ? 0 : 1;
                _time = _remainTime;
                _remainTime = 0;
                _curTargetVal = _targetVal;
                _dt = 0;
                _twice = false;
            }
            return _dt >= _time;
        }
    }

    /// <summary>
    /// NImage Fill
    /// </summary>
    internal sealed class TweenImgFillObj : TweenObjectBase
    {
        private NImage m_Image;
        private float _startVal;
        private float _targetVal;
        private float _time;

        public static TweenImgFillObj Create(NImage image, float targetVal, float time)
        {
            TweenImgFillObj obj = new TweenImgFillObj
            {
                gameObject = image.gameObject,
                m_Image = image,
                _startVal = image.fillAmount,
                _targetVal = targetVal,
                _time = time,
                _dt = 0,
            };
            return obj;
        }

        public override bool Execute(float dt)
        {
            _dt += dt;
            float val = Mathf.Lerp(_startVal, _targetVal, _dt / _time);
            m_Image.fillAmount = val;
            return _dt >= _time;
        }
    }
}